//
// Created by 王乐 on 2022/2/18.
//

#include "LePostProcessingBloomRenderer.h"
#include "glad/glad.h"

#include "LeVBOManager.h"

#include "LeCameraManager.h"
#include "LeScreenSettings.h"

#include <iostream>

#include "LeInput.h"

LePostProcessingBloomRenderer::LePostProcessingBloomRenderer()
{

}

LePostProcessingBloomRenderer::~LePostProcessingBloomRenderer()
{

}

void LePostProcessingBloomRenderer::preLoopRender()
{
    shaderBlur.initShaderProgram("/Users/wangle/CLionProjects/Leste/res/shader/blur.vs",
                                 "/Users/wangle/CLionProjects/Leste/res/shader/blur.fs");


    unsigned int VBO;
    LeVBOManager::findVBO("ScreenQuad", VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));

    // Set up floating point framebuffer to render scene to
    glGenFramebuffers(1, &hdrFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
    glGenTextures(2, colorBuffers);
    for (unsigned int i = 0; i < 2; i++)
    {
        glBindTexture(GL_TEXTURE_2D, colorBuffers[i]);
        glTexImage2D(
                GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH*2, SCR_HEIGHT*2, 0, GL_RGB, GL_FLOAT, NULL
        );
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        // attach texture to framebuffer
        glFramebufferTexture2D(
                GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, colorBuffers[i], 0
        );
    }

    // create and attach depth buffer (renderbuffer)
    glGenRenderbuffers(1, &rboDepth);
    glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, SCR_WIDTH*2, SCR_HEIGHT*2);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);
    // tell OpenGL which color attachments we'll use (of this framebuffer) for rendering
    unsigned int attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
    glDrawBuffers(2, attachments);
    // finally check if framebuffer is complete
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        std::cout << "Framebuffer not complete!" << std::endl;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // ping-pong-framebuffer for blurring
    glGenFramebuffers(2, pingpongFBO);
    glGenTextures(2, pingpongColorbuffers);
    for (unsigned int i = 0; i < 2; i++)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]);
        glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[i]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH*2, SCR_HEIGHT*2, 0, GL_RGBA, GL_FLOAT, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // we clamp to the edge as the blur filter would otherwise sample repeated texture values!
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongColorbuffers[i], 0);
        // also check if framebuffers are complete (no need for depth buffer)
        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            std::cout << "Framebuffer not complete!" << std::endl;
    }

    shaderBlur.useShader();
    shaderBlur.setInt("image", 0);

    m_Shader.useShader();
    m_Shader.setInt("scene", 0);
    m_Shader.setInt("bloomBlur", 1);

    amount = 10;
}

void LePostProcessingBloomRenderer::postprocessingSetup()
{
    // render
    // ------
    // bind to framebuffer and draw scene as we normally would to color texture
    glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
    glEnable(GL_DEPTH_TEST); // enable depth testing (is disabled for rendering screen-space quad)
}

void LePostProcessingBloomRenderer::render()
{
//    // now bind back to default framebuffer and draw a quad plane with the attached framebuffer color texture
//    glBindFramebuffer(GL_FRAMEBUFFER, 0);
//
//    // 2. blur bright fragments with two-pass Gaussian Blur
//    // --------------------------------------------------
//
//
//    bool horizontal = true, first_iteration = true;
//    if(LeInput::getKey(GLFW_KEY_1) == GLFW_PRESS)
//        amount = 10;
//    if(LeInput::getKey(GLFW_KEY_2) == GLFW_PRESS)
//        amount = 50;
//    if(LeInput::getKey(GLFW_KEY_3) == GLFW_PRESS)
//        amount = 100;
//    shaderBlur.useShader();
//    for (unsigned int i = 0; i < amount; i++)
//    {
//        glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]);
//        shaderBlur.setInt("horizontal", horizontal);
//        glActiveTexture(GL_TEXTURE0);
//        glBindTexture(GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongColorbuffers[!horizontal]);  // bind texture of other framebuffer (or scene if first iteration)
//        renderQuad();
//        horizontal = !horizontal;
//        if (first_iteration)
//            first_iteration = false;
//    }
//    glBindFramebuffer(GL_FRAMEBUFFER, 0);
//
//// 3. now render floating point color buffer to 2D quad and tonemap HDR colors to default framebuffer's (clamped) color range
//    // --------------------------------------------------------------------------------------------------------------------------
//    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//    m_Shader.useShader();
//    glActiveTexture(GL_TEXTURE0);
//    glBindTexture(GL_TEXTURE_2D, colorBuffers[0]);
//    glActiveTexture(GL_TEXTURE1);
//    glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[!horizontal]);
//    bool bloom = true;
//    float exposure = 1.0f;
//    m_Shader.setInt("bloom", bloom);
//    m_Shader.setFloat("exposure", exposure);
//    renderQuad();
}

void LePostProcessingBloomRenderer::postLoopRender()
{
    glDeleteFramebuffers(1, &hdrFBO);
}

void LePostProcessingBloomRenderer::renderQuad()
{
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 6);
}

void LePostProcessingBloomRenderer::postprocessingRender()
{
    // now bind back to default framebuffer and draw a quad plane with the attached framebuffer color texture
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // 2. blur bright fragments with two-pass Gaussian Blur
    // --------------------------------------------------


    bool horizontal = true, first_iteration = true;
    if(LeInput::getKey(GLFW_KEY_1) == GLFW_PRESS)
        amount = 10;
    if(LeInput::getKey(GLFW_KEY_2) == GLFW_PRESS)
        amount = 50;
    if(LeInput::getKey(GLFW_KEY_3) == GLFW_PRESS)
        amount = 100;
    shaderBlur.useShader();
    for (unsigned int i = 0; i < amount; i++)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]);
        shaderBlur.setInt("horizontal", horizontal);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongColorbuffers[!horizontal]);  // bind texture of other framebuffer (or scene if first iteration)
        renderQuad();
        horizontal = !horizontal;
        if (first_iteration)
            first_iteration = false;
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

// 3. now render floating point color buffer to 2D quad and tonemap HDR colors to default framebuffer's (clamped) color range
    // --------------------------------------------------------------------------------------------------------------------------
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    m_Shader.useShader();
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, colorBuffers[0]);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[!horizontal]);
    bool bloom = true;
    float exposure = 1.0f;
    m_Shader.setInt("bloom", bloom);
    m_Shader.setFloat("exposure", exposure);
    renderQuad();
}
